package cn.keniykey.mc.landterritory.land;

import java.sql.ResultSet;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeSet;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.Nullable;

import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.entity.Player;

import cn.keniykey.mc.landterritory.LandTerritoryMain;
import cn.keniykey.mc.landterritory.config.ConfigurationManager;
import cn.keniykey.mc.landterritory.config.LanguageManager;
import cn.keniykey.mc.landterritory.database.AbDataBaseCore;
import cn.keniykey.mc.landterritory.database.DataBaseManager;
import cn.keniykey.mc.landterritory.database.EDataBaseFormEnum;
import cn.keniykey.mc.landterritory.land.api.ELandType;
import cn.keniykey.mc.landterritory.land.api.ILandManager;
import cn.keniykey.mc.landterritory.land.api.Land;
import cn.keniykey.mc.landterritory.land.api.LandPoint;
import cn.keniykey.mc.landterritory.land.api.LandPosition;
import cn.keniykey.mc.landterritory.land.api.LandRange;
import cn.keniykey.mc.landterritory.landflag.LandFlagManager;
import cn.keniykey.mc.landterritory.toolkit.DebugUtil;

public final class LandManager implements ILandManager{
	private HashMap<Long,Land> hashmap_id_lands = new HashMap<>();//ID领地列表
	private HashMap<ELandType,ArrayList<Land>> hashmap_all_lands = new HashMap<>();//所有领地
	private HashMap<String,Land> hashmap_name_lands = new HashMap<>();//领地名对应领地
	private ArrayList<Land> list_sold_lands = new ArrayList<>();//出售中的领地
	
	HashMap<UUID,Integer> hashmap_players_maxlimit = new HashMap<>();//玩家领地上限
	private HashMap<UUID,ArrayList<Land>> hashmap_players_lands = new HashMap<>();//玩家持有领地
	ConcurrentHashMap<UUID, Land> conmap_players_in = new ConcurrentHashMap<>();//玩家所在领地[定时循环检测]
	
	private static final List<Integer> list_savelevels = Arrays.asList(15,30,60,70,100,300,400,500,600,700,800
			,1000,2000,3000,4000,5000,6000,7000,8000,9000,10000,50000,0);
	private HashMap<String,ArrayList<HashMap<Integer,HashMap<Integer,HashMap<Integer,HashSet<Land>>>>>> hashmap_landhash = new HashMap<>();
	private HashMap<LandPoint, TreeSet<Land>> hashmap_buffer_pos = new HashMap<>();
	private int buffer_pos_size = 0;
	
	private LandManager() {
		for(final ELandType type : ELandType.values()) hashmap_all_lands.put(type, new ArrayList<>());
	}
	
	private static LandManager manager = new LandManager();
	
	public static LandManager getLandManager() {
		return manager;
	}
	
	private static int coverXYZ(final int xyz, final int save_size) {
		if(save_size == 0) return 0;
		if(xyz >= 0) return (xyz / save_size);
		return (xyz / (save_size - 1));
	}
	private static int getSaveLevelIndex(final LandRange range) {
		for(int index = 0,maxindex = (list_savelevels.size()-1); index < maxindex; index++) {
			int save_size = list_savelevels.get(index);
			final LandPoint p1 = range.getLandPointOne();
			final LandPoint p2 = range.getLandPointTwo();
			if(coverXYZ(p1.getX(), save_size) != coverXYZ(p2.getX(), save_size)) continue;
			if(coverXYZ(p1.getY(), save_size) != coverXYZ(p2.getY(), save_size)) continue;
			if(coverXYZ(p1.getZ(), save_size) != coverXYZ(p2.getZ(), save_size)) continue;
			return index;
		}
		return (list_savelevels.size()-1);
	}
	
	public void addLandInMemory(Land land) {
		hashmap_id_lands.put(land.getID(), land);
		hashmap_name_lands.put(land.getName(), land);
		hashmap_all_lands.get(land.getType()).add(land);
		if(land.getPrice() >= 0) list_sold_lands.add(land);
		final UUID owner = land.getOwner();
		final ArrayList<Land> player_lands;
		if(hashmap_players_lands.containsKey(owner)) {
			player_lands = hashmap_players_lands.get(owner);
		}else {
			player_lands = new ArrayList<>();
			hashmap_players_lands.put(owner, player_lands);
		}
		player_lands.add(land);
	}
	public void removeLandInMemory(Land land) {
		hashmap_id_lands.remove(land.getID());
		hashmap_name_lands.remove(land.getName());
		final ArrayList<Land> alllands = hashmap_all_lands.get(land.getType());
		alllands.remove(alllands.indexOf(land));
		if(land.getPrice() >= 0) list_sold_lands.remove(list_sold_lands.indexOf(land));
		final UUID owner = land.getOwner();
		if(hashmap_players_lands.containsKey(owner)) {
			final ArrayList<Land> player_lands = hashmap_players_lands.get(owner);
			player_lands.remove(player_lands.indexOf(land));
			if(player_lands.isEmpty()) hashmap_players_lands.remove(owner);
		}
	}
	private void resetBufferPos() {
		if(buffer_pos_size == 0) return;
		final HashMap<LandPoint, TreeSet<Land>> oldmap = hashmap_buffer_pos;
		hashmap_buffer_pos = new HashMap<>();
		buffer_pos_size = 0;
		Bukkit.getScheduler().runTaskAsynchronously(LandTerritoryMain.getPlugin(), ()->{
			oldmap.clear();
		});
	}
	public void addLandInHash(final LandRange range,final Land land) {
		range.fit();
		final int save_idnex = getSaveLevelIndex(range);
		final int save_size = list_savelevels.get(save_idnex);
		final LandPoint p1 = range.getLandPointOne();
		final String worldname = p1.getWorldName();
		final int cx = coverXYZ(p1.getX(), save_size);
		final int cy = coverXYZ(p1.getY(), save_size);
		final int cz = coverXYZ(p1.getZ(), save_size);
		if(!hashmap_landhash.containsKey(worldname)) {
			ArrayList<HashMap<Integer,HashMap<Integer,HashMap<Integer,HashSet<Land>>>>> list_landhash_savesize = new ArrayList<>(list_savelevels.size());
			hashmap_landhash.put(worldname, list_landhash_savesize);
			for(int index = 0,maxindex = list_savelevels.size(); index < maxindex; index++) {
				list_landhash_savesize.add(new HashMap<>());
			}
		}
		final HashMap<Integer,HashMap<Integer,HashMap<Integer,HashSet<Land>>>> hash_x = hashmap_landhash.get(worldname).get(save_idnex);
		final HashMap<Integer,HashMap<Integer,HashSet<Land>>> hash_z;
		if(hash_x.containsKey(cx)) {
			hash_z = hash_x.get(cx);
		}else {
			hash_z = new HashMap<>();
			hash_x.put(cx, hash_z);
		}
		final HashMap<Integer,HashSet<Land>> hash_y;
		if(hash_z.containsKey(cz)) {
			hash_y = hash_z.get(cz);
		}else {
			hash_y = new HashMap<>();
			hash_z.put(cz, hash_y);
		}
		final HashSet<Land> hash;
		if(hash_y.containsKey(cy)) {
			hash = hash_y.get(cy);
		}else {
			hash = new HashSet<>();
			hash_y.put(cy, hash);
		}
		hash.add(land);
		resetBufferPos();
	}
	public void removeLandInHash(final LandRange range,final Land land) {
		range.fit();
		final int save_idnex = getSaveLevelIndex(range);
		final int save_size = list_savelevels.get(save_idnex);
		final LandPoint p1 = range.getLandPointOne();
		final String worldname = p1.getWorldName();
		final int cx = coverXYZ(p1.getX(), save_size);
		final int cy = coverXYZ(p1.getY(), save_size);
		final int cz = coverXYZ(p1.getZ(), save_size);
		if(hashmap_landhash.containsKey(worldname)) {
			final HashMap<Integer,HashMap<Integer,HashMap<Integer,HashSet<Land>>>> hash_x = hashmap_landhash.get(worldname).get(save_idnex);
			if(hash_x.containsKey(cx)) {
				final HashMap<Integer,HashMap<Integer,HashSet<Land>>> hash_z = hash_x.get(cx);
				if(hash_z.containsKey(cz)) {
					final HashMap<Integer,HashSet<Land>> hash_y = hash_z.get(cz);
					if(hash_y.containsKey(cy)) {
						final HashSet<Land> hash = hash_y.get(cy);
						hash.remove(land);
						if(hash.isEmpty()) hash_y.remove(cy);
					}
					if(hash_y.isEmpty()) hash_z.remove(cz);
				}
				if(hash_z.isEmpty()) hash_x.remove(cx);
			}
		}
		resetBufferPos();
	}
	public TreeSet<Land> getLandsInHash(final Location loc) {
		return getLandsInHash(new LandPoint(loc));
	}
	public TreeSet<Land> getLandsInHash(final LandPoint point) {
		if(hashmap_buffer_pos.containsKey(point)) return hashmap_buffer_pos.get(point);
		final String worldname = point.getWorldName();
		final int x = point.getX();
		final int y = point.getY();
		final int z = point.getZ();
		final TreeSet<Land> treeset_lands = new TreeSet<>();
		if(!hashmap_landhash.containsKey(worldname)) return treeset_lands;
		for(int index = 0,maxindex = hashmap_landhash.get(worldname).size(); index < maxindex; index++) {
			final int save_size = list_savelevels.get(index);
			final int cx = coverXYZ(x, save_size);
			final int cy = coverXYZ(y, save_size);
			final int cz = coverXYZ(z, save_size);
			final HashMap<Integer,HashMap<Integer,HashMap<Integer,HashSet<Land>>>> hash_x = hashmap_landhash.get(worldname).get(index);
			if(!hash_x.containsKey(cx)) continue;
			final HashMap<Integer,HashMap<Integer,HashSet<Land>>> hash_z = hash_x.get(cx);
			if(!hash_z.containsKey(cz)) continue;
			final HashMap<Integer,HashSet<Land>> hash_y = hash_z.get(cz);
			if(!hash_y.containsKey(cy)) continue;
			final HashSet<Land> hash = hash_y.get(cy);
			for(final Land land : hash) {
				if(land.getRange().isInRange(point)) treeset_lands.add(land);
			}
		}
		hashmap_buffer_pos.put(point, treeset_lands);
		buffer_pos_size++;
		if(buffer_pos_size > 1000) {
			buffer_pos_size -= 100;
			int count = 0;
			final Iterator<Entry<LandPoint, TreeSet<Land>>> it = hashmap_buffer_pos.entrySet().iterator();
			while(count < 100 && it.hasNext()) {
				final Entry<LandPoint, TreeSet<Land>> en = it.next();
				if(en.getKey().equals(point)) continue;
				en.getValue().clear();
				it.remove();
				count++;
			}
		}
		return treeset_lands;
	}
	public LinkedList<Land> getCollisionLands(LandRange range) {
		final LinkedList<Land> linked_lands = new LinkedList<>();
		for(final Land land : this.hashmap_name_lands.values()) {
			if(range.cover(land.getRange())) linked_lands.add(land);
		}
		return linked_lands;
	}
	@Nullable
	@Override
	public Land getHighestPriorityLand(LandPoint point) {
		final TreeSet<Land> treeset_lands = getLandsInHash(point);
		if(treeset_lands.isEmpty()) return null;
		final Iterator<Land> it = treeset_lands.iterator();
		while(it.hasNext()) {
			final Land land = it.next();
			if(!land.isDeleted()) return land;
		}
		return null;
	}
	@Nullable
	@Override
	public Land getCircleDetectionLand(Player player) {
		return conmap_players_in.get(player.getUniqueId());
	}
	public List<Land> getPlayerLands(UUID player_uuid){
		if(!hashmap_players_lands.containsKey(player_uuid)) return Collections.emptyList();
		return hashmap_players_lands.get(player_uuid);
	}
	@Nullable
	public Land getLand(long id) {
		if(!hashmap_id_lands.containsKey(id)) return null;
		return hashmap_id_lands.get(id);
	}
	@Nullable
	public Land getLand(String name) {
		if(!hashmap_name_lands.containsKey(name)) return null;
		return hashmap_name_lands.get(name);
	}
	public void changeLandOwner(Land land,UUID new_owner) {
		if(hashmap_players_lands.containsKey(land.getOwner())) {
			final ArrayList<Land> list_lands = hashmap_players_lands.get(land.getOwner());
			list_lands.remove(list_lands.indexOf(land));
			if(list_lands.isEmpty()) hashmap_players_lands.remove(land.getOwner());
		}
		final ArrayList<Land> list_lands;
		if(hashmap_players_lands.containsKey(new_owner)) {
			list_lands = hashmap_players_lands.get(new_owner);
		}else {
			list_lands = new ArrayList<>();
			hashmap_players_lands.put(new_owner, list_lands);
		}
		list_lands.add(land);
		land.setOwner(new_owner);
	}
	public void changeLandName(Land land,String name) {
		hashmap_name_lands.remove(land.getName());
		land.setName(name);
		hashmap_name_lands.put(name, land);;
	}
	public void changeLandTypeCommunity(Land land) {
		if(!ELandType.TERRITORY.equals(land.getType())) return;
		final ArrayList<Land> list_territory = hashmap_all_lands.get(ELandType.TERRITORY);
		list_territory.remove(list_territory.indexOf(land));
		land.setType(ELandType.COMMUNITY);
		hashmap_all_lands.get(ELandType.COMMUNITY).add(land);
	}
	public ArrayList<Land> getSoldLands() {
		return list_sold_lands;
	}
	public List<Land> getServerLands(ELandType type) {
		final ArrayList<Land> list_lands = hashmap_all_lands.get(type);
		if(list_lands.isEmpty()) return Collections.emptyList();
		return Collections.unmodifiableList(list_lands);
	}
	public int getPlayerLandLimit(UUID player_uuid) {
		if(Bukkit.getPlayer(player_uuid) != null) {
			if(hashmap_players_maxlimit.containsKey(player_uuid)) return hashmap_players_maxlimit.get(player_uuid);
			return -1;
		}
		try(ResultSet rs = DataBaseManager.getDataBaseCore().getPlayer(player_uuid)){
			if(rs.next()) return rs.getInt("amount");
		}catch(Exception ex) {
			ex.printStackTrace();
			DebugUtil.info("[LandManager]数据库获取指令玩家领地上限指令异常：无法关闭Rs");
		}
		return -1;
	}
	public void setPlayerLandLimit(UUID player_uuid, int amount) {
		if(amount < 1) return;
		try {
			DataBaseManager.getDataBaseCore().updatePlayer(player_uuid, amount);
			if(Bukkit.getPlayer(player_uuid) != null) hashmap_players_maxlimit.put(player_uuid, amount);
		}catch(Exception ex) {
			ex.printStackTrace();
		}
	}
	@Nullable
	public Land createLand(UUID player_uuid,String landname,ELandType type,LandRange range,LandPosition pos) {
		final int level;
		switch(type) {
		case TERRITORY:
			level = ConfigurationManager.Territory_Create_Level;
			break;
		case COMMUNITY:
			level = ConfigurationManager.Community_Create_Level;
			break;
		case GROUND:
			level = ConfigurationManager.Ground_Create_Level;
			break;
		default:
			throw new RuntimeException("Unknown LandType(未知领地类型)");
		}
		final HashMap<String,Set<String>> landflags = LandFlagManager.getFlagManager().getInitialLandFlags(type);
		final long id;
		try {
			id = DataBaseManager.getDataBaseCore().initLand(player_uuid, landname, type.getID(), range, pos, level, -1, false, false);
		}catch(Exception ex) {
			ex.printStackTrace();
			DebugUtil.info("[LandManager]数据库接口指令无法新增领地：" + landname);
			return null;
		}
		if(id < 0l) {
			DebugUtil.info("[LandManager]数据库接口指令无法新增领地，获取不到领地ID：" + landname);
			return null;
		}
		final Land land = new Land(id, player_uuid, landname, type, landflags, range, pos, level, -1, false, false);
		land.setEnterMessage(LanguageManager.getLang(1002010, false));
		land.setLeaveMessage(LanguageManager.getLang(1002011, false));
		try {
			DataBaseManager.getDataBaseCore().initLandMessage(id, land.getEnterMessage(), land.getLeaveMessage());
		}catch(Exception ex) {
			ex.printStackTrace();
			DebugUtil.info("[LandManager]数据库接口指令无法新建进出提示：" + landname);
			try {
				DataBaseManager.getDataBaseCore().deleteLand(id);
			}catch(Exception exx) {
				exx.printStackTrace();
				DebugUtil.info("[LandManager]因为创建失败而删除，数据库接口指令无法删除领地ID：" + String.valueOf(id));
			}
			return null;
		}
		try {
			DataBaseManager.getDataBaseCore().updateLand(id, EDataBaseFormEnum.LANDDATA_FLAGS, 
					AbDataBaseCore.jsonFromFlags(new HashSet<>(landflags.keySet())));
			final HashMap<String,HashSet<String>> flag_pars = new HashMap<>();
			for(Entry<String, Set<String>> entry : landflags.entrySet()) {
				if(entry.getValue().isEmpty()) continue;
				flag_pars.put(entry.getKey(), new HashSet<>(entry.getValue()));
			}
			DataBaseManager.getDataBaseCore().updateLand(id, EDataBaseFormEnum.LANDDATA_PARAMETER,
					AbDataBaseCore.jsonFromFlagsParameter(flag_pars));
		}catch(Exception ex) {
			ex.printStackTrace();
			DebugUtil.info("[LandManager]无法初始化领地标记，领地ID：" + String.valueOf(id));
		}
		land.build();
		addLandInMemory(land);
		addLandInHash(range, land);
		return land;
	}
	public static void clearLands() {
		final LandManager man = getLandManager();
		man.hashmap_id_lands.clear();
		for(ArrayList<Land> list : man.hashmap_all_lands.values()) list.clear();
		man.hashmap_name_lands.clear();
		man.list_sold_lands.clear();
		for(ArrayList<Land> list : man.hashmap_players_lands.values()) list.clear();
		man.hashmap_players_lands.clear();
		man.hashmap_players_maxlimit.clear();
		man.conmap_players_in.clear();
		if(!man.hashmap_landhash.isEmpty()) {
			for(final ArrayList<HashMap<Integer, HashMap<Integer, HashMap<Integer, HashSet<Land>>>>> list_levels : man.hashmap_landhash.values()) {
				if(list_levels.isEmpty()) continue;
				for(HashMap<Integer, HashMap<Integer, HashMap<Integer, HashSet<Land>>>> hashx : list_levels) {
					if(hashx.isEmpty()) continue;
					for(HashMap<Integer, HashMap<Integer, HashSet<Land>>> hashy : hashx.values()) {
						if(hashy.isEmpty()) continue;
						for(HashMap<Integer, HashSet<Land>> hashz : hashy.values()) {
							if(hashz.isEmpty()) continue;
							for(HashSet<Land> hashlands : hashz.values()) hashlands.clear();
							hashz.clear();
						}
						hashy.clear();
					}
					hashx.clear();
				}
				list_levels.clear();
			}
		}
		man.hashmap_landhash.clear();
		man.resetBufferPos();
	}
	public static void loadLands() {
		clearLands();
		int start = 0,limit = 0;
		try {
			do {
				start = limit;
				limit += 100;
				try(final ResultSet rs_lands = DataBaseManager.getDataBaseCore().getLands(start, limit)) {
					while(rs_lands.next()) {
						final long id = rs_lands.getLong("id");
						final UUID owner = UUID.fromString(rs_lands.getString("owner"));
						final ELandType type = ELandType.getELandType(rs_lands.getInt("type"));
						final LandRange range = LandRange.FromJson(rs_lands.getString("range"));
						final LandPosition pos = LandPosition.FromJson(rs_lands.getString("pos"));
						final String json_flags = rs_lands.getString("flags");
						final String json_parm = rs_lands.getString("parameter");
						final HashSet<String> hashset_flags = json_flags == null ? new HashSet<>() : AbDataBaseCore.jsonToFlags(json_flags);
						final HashMap<String,HashSet<String>> hashset_parms = json_parm == null ? new HashMap<>() : AbDataBaseCore.jsonToFlagsParameter(json_parm);
						
						final HashMap<String,Set<String>> landflags = new HashMap<>(hashset_flags.size());
						for(final String flagname : hashset_flags) landflags.put(flagname, Collections.emptySet());
						for(final Entry<String, HashSet<String>> entry : hashset_parms.entrySet()) {
							if(entry.getValue().isEmpty()) continue;
							landflags.put(entry.getKey(), entry.getValue());
						}
						final Land land = new Land(id, owner, rs_lands.getString("name"), type, landflags, range, pos,
								rs_lands.getInt("level"), rs_lands.getInt("price"), rs_lands.getBoolean("overlap"), rs_lands.getBoolean("system"));
						try(final ResultSet rs_message = DataBaseManager.getDataBaseCore().getLandMessage(id)) {
							land.setEnterMessage(rs_message.getString("entertip"));
							land.setLeaveMessage(rs_message.getString("leavetip"));
						}catch(Exception ex) {
							ex.printStackTrace();
						}
						land.build();
						LandManager.getLandManager().addLandInMemory(land);
						LandManager.getLandManager().addLandInHash(range, land);
						start++;
					}
				}
			}while(start >= limit);
		}catch(Exception ex) {
			ex.printStackTrace();
			return;
		}
	}
}